package GUI;

import java.util.Iterator;

import TDAArbol.EmptyTreeException;
import TDAArbol.LinkedTree;
import TDAArbol.Tree;
import TDACola.EmptyQueueException;
import TDACola.LinkedQueue;
import TDACola.Queue;
import TDAColaCP.EmptyPriorityQueueException;
import TDAColaCP.HeapPQueue;
import TDADiccionario.Dictionary;
import TDALista.BoundaryViolationException;
import TDALista.InvalidPositionException;
import TDALista.ListaSE;
import TDALista.Position;
import TDALista.PositionList;
import TDAMapeo.Entry;
import TDAMapeo.InvalidKeyException;
import TDAMapeo.Map;
import TDAPila.ArrayStack;
import TDAPila.Stack;

/**
 * logica para el mainframe, ejercicios del punto H
 * @author Jordan
 *
 */
public class logic {
	
		
//	PUNTO 1
	/**
	 * Crea arbol con una raiz como unico elemento
	 * @param rotulo : raiz del arbol
	 * @return arbol hoja
	 */
	public LinkedTree<Integer> cargararbol(Integer rotulo){
		LinkedTree<Integer> nuevo=new LinkedTree<Integer>(rotulo);
		return nuevo;
	}
	
//	PUNTO 2
	
	protected void insertarNodoComoPrimerHijo(Integer i, Object newNode, Tree<Integer> tree){
		try{
			Position<Integer> padrechosen = busquedaNodo(i, tree);
	        tree.addLastChild(padrechosen,
	                Integer.valueOf(newNode.toString()));
		}catch (InvalidPositionException e){e.printStackTrace();}
		
	}
	
	/**
	 * Metodo que dado un rotulo hara una busqueda no exahustiva en el arbol para encontrar el nodo que lo contiene.
	 * Si no existe el rotulo en el arbol lanzar excepcion
	 * @param r : rotulo que se va a buscar
	 * @return Nodo que contiene al rotulo que se paso como parametro.
	 * @throws InvalidPositionException : lanza si no encuentra al rotulo en el arbol
	 */
	protected Position<Integer> busquedaNodo(Integer r, Tree<Integer> tree) throws InvalidPositionException{		
		Position<Integer> nod=null;
		Iterator<Position<Integer>> iter=tree.positions().iterator();
		boolean noEncontrado=true;
		while(iter.hasNext() && noEncontrado){
			Position<Integer> nodaux=iter.next();
			int aux=((Comparable<Integer>)r).compareTo(nodaux.element());
			if(aux==0){
				noEncontrado=false;
				nod=nodaux;
			}
		}
		if (nod==null)
			throw new InvalidPositionException("El rotulo no pertenece al arbol");
		return nod;
		
				
	}
	
//	PUNTO 3
	
	/**
	 * recorrido en preorden del arbol
	 * @param list lista donde se guardan los elementos de las posiciones recorridas
	 * @param root raiz del arbol
	 */
	private void preOrdenSoloElementos(ListaSE<Integer> list, Position<Integer> root, Tree<Integer> tree){
		list.addLast(root.element());
		try {
			for (Position<Integer> h: tree.children(root))
				preOrdenSoloElementos(list, h, tree);
		} catch (InvalidPositionException e) {
			e.printStackTrace();
		}
	}
	
//	PUNTO 4
	
	/**
	 * recorrido en posorden del arbol
	 * @param list lista donde se guarda los elementos de las posiciones recorridas
	 * @param root raiz del arbol
	 */
	private void posOrdenSoloElementos(ListaSE<Integer> list, Position<Integer> root, Tree<Integer> tree){
		try {
			for (Position<Integer> h: tree.children(root))
				posOrdenSoloElementos(list, h, tree);
		} catch (InvalidPositionException e) {
			e.printStackTrace();
		}
		list.addLast(root.element());
	}
	
//	PUNTO 5
	
	/**
	 * Devuelve una lista con los nodos separados por nivel
	 * @param lista PositionList<E> lista donde se va a listar los rotulos del arbol (por nivel)
	 * @throws EmptyTreeException
	 */	
	private void ListaRotulosporniveles(PositionList<Integer> lista, Tree<Integer> tree){
		LinkedQueue<Position<Integer>> c=new LinkedQueue<Position<Integer>>();
		try {
			c.enqueue(tree.root());
			c.enqueue(null);
			while(!tree.isEmpty() && !c.isEmpty()){
				Position<Integer> p=c.dequeue();
				if (p==null){
					if (!c.isEmpty()){
						c.enqueue(null);
						lista.addLast(null);
					}
				}
				else{
					lista.addLast(p.element());
					for(Position<Integer> h: tree.children(p))
						c.enqueue(h);
				}
			}
		} catch (InvalidPositionException e) {
			e.printStackTrace();
		} catch (EmptyTreeException e) {
			e.printStackTrace();
		} catch (EmptyQueueException e){
			e.printStackTrace();
		}
	}
	
	/**
	 * Devuelve una lista con los rotulos del arbol ordenados segun el parametro entero que recibe:
	 * 1 : pre-orden
	 * 2 : post-orden
	 * 3 : niveles
	 * 
	 * @param orden
	 * @return lista con los rotulos del arbol
	 * @throws IllegalArgumentException : si el parametro es diferente de 1, 2 o 3.
	 * @throws EmptyTreeException : si el arbol esta vacio.
	 */
	protected PositionList<Integer> ListaRotulosOrdenada(int orden, Tree<Integer> tree) throws IllegalArgumentException,EmptyTreeException{
		//1: preorden
		//2: posorden
		//3: por nivel
		if(tree.isEmpty()) throw new EmptyTreeException("el arbol esta vacio");
		ListaSE<Integer> nuevalista=new ListaSE<Integer>();
		switch (orden) {
		case 1: 
			preOrdenSoloElementos(nuevalista, tree.root(), tree);
			break;
		
		case 2: 
			posOrdenSoloElementos(nuevalista, tree.root(), tree);
			break;
		
		case 3: 
			nuevalista.addFirst(null);
			ListaRotulosporniveles(nuevalista, tree);
			break;
		default: throw new IllegalArgumentException("ingrese un numero del 1 al 3"); 	

		}
		return nuevalista;
	}
	
//	PUNTO 6
	 /**
     * @param colaAOrdenar Cola a la cual se le ordenaran los rotulos
     * @return lista con los rotulos del arbol en orden descendente.
     * @throws EmptyPriorityQueueException Lanzada en caso de que la cola este vacna.
     */
    protected PositionList<Integer> ordenDescendente(
            HeapPQueue<Integer, Integer> colaAOrdenar)
            throws EmptyPriorityQueueException {
        PositionList<Integer> resIterable = new ListaSE<Integer>();
        while (!colaAOrdenar.isEmpty()) {
            resIterable.addFirst(colaAOrdenar.removeMin().getKey());
        }
        return resIterable;
    }
    
//    PUNTO 7
    /**
	 * Elimina todos los nodos de un nivel que es pasado como parametro, los rotulos de los nodos eliminados
	 * son devueltos en una pila
	 * @param nivel entero
	 * @return pila con los rotulos de los nodos eliminados
	 * @throws InvalidPositionException 
	 * @throws EmptyTreeException si el arbol esta vacio
	 * @throws IllegalArgumentException si el nivel pasado como parametro es menor a 1 o mayor a la altra del arbol
	 */
	protected Stack<Integer> EliminarNivel(int nivel, Tree<Integer> tree)throws InvalidPositionException, EmptyTreeException, IllegalArgumentException{
		if (tree.isEmpty()) 
			throw new EmptyTreeException("el arbol esta vacio");
		if (nivel==0) 
			throw new IllegalArgumentException("No se puede eliminar la raiz");
		if (nivel>alturaArbol(tree)) 
			throw new IllegalArgumentException("El nivel ingresado es mayor a la altura del arbol");
		if (nivel<0) 
			throw new IllegalArgumentException("El nivel no puede ser negativo");
		
		Queue<Position<Integer>> c=new LinkedQueue<Position<Integer>>();
		Stack<Integer> pila=new ArrayStack<Integer>();
		try {
			c.enqueue(tree.root());
			c.enqueue(null);
			int cont=0;
			while(!c.isEmpty() && cont<=nivel){
				Position<Integer> n=c.dequeue();
				if (n==null){
					if (!c.isEmpty()){
						c.enqueue(null);
						cont++;
					}
				}
				else{
					for(Position<Integer> h: tree.children(n))
						c.enqueue(h);
					if (cont==nivel){
						pila.push(n.element());
						tree.removeNode(n);
					}
				}
			
			}
			return	pila;	
		} catch (EmptyQueueException e) {
			throw new InvalidPositionException("..");
		}
		
	}
	
//	PUNTO 8
	
	/**
	 * Metodo que dados dos rotulos PERTENECIENTES al arbol, devolvera el camino que hay entre los nodos que los contienen, si es
	 * que los nodos son ancestro y descendiente entre si. Pila de Nodos o Elementos?
	 * @param r1 : rotulo1 
	 * @param r2 : rotulo2
	 * @return pila con el camino entre los dos rotulos, si no hay camino entre ellos devuelve nulo.
	 */
	protected Stack<Position<Integer>> elCaminoDelRotulo(Integer r1, Integer r2, Tree<Integer> tree)throws InvalidPositionException{
		Position<Integer> nodo1=busquedaNodo(r1, tree);
		Position<Integer> nodo2=busquedaNodo(r2, tree);

		Stack<Position<Integer>> camino;
		Stack<Position<Integer>> camino1=new ArrayStack<Position<Integer>>();
		Stack<Position<Integer>> camino2=new ArrayStack<Position<Integer>>();
		if(esAncestroDe(nodo1, nodo2, camino1, tree))
			camino=camino1;
		else{
			if(esAncestroDe(nodo2, nodo1, camino2, tree))
				camino=camino2;
			else
				//camino=new Pila<Position<E>>();//devuelvo una pila vacia
				//Los nodos no son ancestros uno del otro, no hay un camino entre ellos.Lanzar Excepcion?
				throw new InvalidPositionException("No hay un camino entre los nodos");
				
		}
		return camino;
	}
	
	
	
//	PUNTO 9
	
	/**
	 * Metodo  que dado dos nodos devuelve verdadero si nodo1 es
	 * ancestro de nodo2, la pila que se pasa como parametro guarda el camino entre ambos.
	 * @param nodo1 : nodo
	 * @param nodo2 : nodo
	 * @param pila : pila en la que se guarda el camino
	 * @return : verdadero si nodo1 es ancestro de nodo2, falso caso contrario
	 * @throws InvalidPositionException : no me acuerdo
	 */
	protected boolean esAncestroDe(Position<Integer> nodo1, Position<Integer> nodo2, Stack<Position<Integer>> pila, Tree<Integer> tree)throws InvalidPositionException{
		try {
			if(nodo2==tree.root() && nodo1!=nodo2){
				//pila.push(nodo2);
				return false;
			}
			else{
				if (nodo1==nodo2){
				pila.push(nodo2);
				return true;
				}
				else {
					try {
						pila.push(nodo2);
						return esAncestroDe(nodo1,tree.parent(nodo2),pila, tree);
					} catch (BoundaryViolationException e) {
						throw new InvalidPositionException("Esto nunca deberia pasar, lo juro");
					}
				}
			}
		} catch (EmptyTreeException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	/**
	 * Devuelve una lista de posiciones del camino de un nodo a la raiz
	 * @param n : {@link Position} nodo a buscar el camino
	 * @return {@link PositionList} :lista de posiciones
	 * @throws InvalidPositionException :si es invalida la posicion.
	 */
	protected PositionList<Position<Integer>> caminoALaRaiz(Position<Integer> n, Tree<Integer> tree) throws InvalidPositionException{
		Stack<Position<Integer>> pila=new ArrayStack<Position<Integer>>();
		PositionList<Position<Integer>> lista=new ListaSE<Position<Integer>>();
		try {
			esAncestroDe(tree.root(), n, pila, tree);
		} catch (EmptyTreeException e1) {
			e1.printStackTrace();
		}
		while(!pila.isEmpty()){
			try {
				lista.addLast(pila.pop());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return lista;
		
	}
    
//	PUNTO 10
    /**
     * deja el arbol de la altura pasada por parametro
     * @param nivel
     */
    protected void podar(int nivel, Tree<Integer> tree){
    	ListaSE<Integer> list=new ListaSE<Integer>();
    	Position<Integer> n=null;
		try {
			n = tree.root();
		} catch (EmptyTreeException e) {
			e.printStackTrace();
		}
		podaraux(tree, nivel, list, n);
    }
    
    private void podaraux(Tree<Integer> tree, int nivel, 
    		ListaSE<Integer> list, Position<Integer> root){
    	try {
			for (Position<Integer> h: tree.children(root))
				podaraux(tree,nivel,list, h);
		} catch (InvalidPositionException e1) {
			e1.printStackTrace();
		}
    	try {
    		if(depth(root, tree)>nivel)
    			tree.removeNode(root);
		} catch (InvalidPositionException e) {
			e.printStackTrace();
		}

	}
    
   
    
//    PUNTO 11
    protected String mapeoRotuloProfundidad(String res, Map<Integer, Integer> map){
    	res += "Mapeo de \n[r\u00f3tulos:alturas]\n";
        for (Entry<Integer, Integer> e : map.entries()) {
            res += " [" + e.getKey().toString() + ":"
                    + e.getValue().toString() + "],\n";
           
        }
        return res;
    }
    
//    PUNTO 12
    
    protected String diccionarioProfundidadRotulo(String res, Dictionary<Integer, Integer> dic){
    	res += "Diccionario de \n[alturas:r\u00f3tulos]\n";
        for (Entry<Integer, Integer> e : dic.entries()) {
            res += " [" + e.getKey().toString() + ":"
                    + e.getValue().toString() + "],\n";
        }
        return res;
           
    }
    
//    PUNTO 13
    
    protected String mapeoProfundidades(Dictionary<Integer, Integer> dic,
    		Map<Integer, Integer> map, Integer chosen){
    	String res="";
    	try{
	    	Integer height = map.get(chosen);
	    	res = "Nodos de la profundidad " + height + ":\n";
	        for (Entry<Integer,Integer> e : dic.entries()) {
	            if(e.getKey() == height) {
	                res += " " + e.getValue().toString() + ",\n";
	            }
	            
	        }
    	}catch(InvalidKeyException e){
    		e.printStackTrace();
    	}
        return res;
    }
    
//    ------------------
    
    /**
   	 * Devuelve la altura del arbol
   	 * @return altura del arbol
   	 * @throws InvalidPositionException nunca, todos los nodos son internos
   	 */
   	protected int alturaArbol(Tree<Integer> tree) throws InvalidPositionException{
   		int h=0;
   		for(Position<Integer> v : tree.positions()){
   			if(tree.isExternal(v))
   				h=Math.max(h, depth(v, tree));
   		}
   		return h;
   		
   	}
    
    /**
	 * devuelve la altura del nodo pasado por parametro.
	 * @param v : {@link Position} nodo a determinar la altura.
	 * @return int : altura
	 * @throws InvalidPositionException :si la posicion es invalida.
	 */
	private int alturaNodo(Position<Integer> v, Tree<Integer> tree) throws InvalidPositionException{
		if (tree.isExternal(v)) 
			return 0;
		int h=0;
		for(Position<Integer> w : tree.children(v))
			h=Math.max(h, alturaNodo(w, tree));
		return 1+h;
	}
    
    protected int depth (Position<Integer> v, Tree<Integer> tree) throws InvalidPositionException{
		try {
			if(tree.isRoot(v))
					return 0;
				else
					return 1+depth(tree.parent(v), tree);
		} catch (BoundaryViolationException e) {
			throw new InvalidPositionException("..");
		}
	}


}
